import { errLog } from "../../../../util/log";
import { StructureCluster, StructureMap, StructurePlan, parseStructures, roomSize } from "../../../../util/structure_map";
import { GroupCluster } from "../cluster";

export const 
    un = undefined, 
    ro = STRUCTURE_ROAD, 
    ex = STRUCTURE_EXTENSION, 
    sp = STRUCTURE_SPAWN, 
    cn = STRUCTURE_CONTAINER,
    st = STRUCTURE_STORAGE,
    tr = STRUCTURE_TERMINAL,
    ps = STRUCTURE_POWER_SPAWN,
    nk = STRUCTURE_NUKER,
    ft = STRUCTURE_FACTORY,
    lb = STRUCTURE_LAB,
    rp = STRUCTURE_RAMPART,
    ln = STRUCTURE_LINK;
export const structureClusterTemplates={
    core: parseStructures([[
        [un,ro,ro,ro,ro,ro,un],
        [ro,ex,ex,ex,ex,ex,ro],
        [ro,sp,un,ex,un,sp,ro],
        [ro,cn,ex,ln,ex,cn,ro],
        [ro,ex,un,ex,un,ex,ro],
        [ro,ex,ex,sp,ex,ex,ro],
        [un,ro,ro,ro,ro,ro,un],
    ],[
        [],
        [],
        [un,rp,un,un,un,rp],
        [],
        [],
        [un,un,un,rp],
        [],
    ]], {x:3, y:3}),
    extensions: parseStructures([[
        [un,un,ro,un,un],
        [un,ro,ex,ro,un],
        [ro,ex,ex,ex,ro],
        [un,ro,ex,ro,un],
        [un,un,ro,un,un],
    ]], {x:2, y:2}),
    storage: parseStructures([[
        [un,ro,ro,un],
        [ro,nk,st,ro],
        [ro,tr,un,ro],
        [ro,ln,ft,ro],
        [un,ro,ro,un],
    ],[
        [],
        [un,rp,rp],
        [un,rp],
        [un,rp,rp],
    ]], {x:2, y:2}),
    lab1: parseStructures([[
        [un,lb,lb,ro],
        [lb,lb,ro,lb],
        [lb,ro,lb,lb],
        [ro,lb,lb,un],
    ],[
        [un,rp,rp],
        [rp,rp,un,rp],
        [rp,un,rp,rp],
        [un,rp,rp],
    ]], {x:2, y:1}),
    lab2: parseStructures([[
        [ro,lb,lb,un],
        [lb,ro,lb,lb],
        [lb,lb,ro,lb],
        [un,lb,lb,ro],
    ],[
        [un,rp,rp],
        [rp,un,rp,rp],
        [rp,rp,un,rp],
        [un,rp,rp],
    ]],{x:1, y:1}),
}

export class ClusterColony extends GroupCluster{
    constructor(spawnRoom = undefined){
        super(spawnRoom);
        if(spawnRoom !== undefined){
            this.map = new StructureMap();
            this.structurePos = [];
            this.init(spawnRoom);
        }
    }
    /**
     * 
     * @abstract
     * @returns {(StructureMap)[]}
     */
    get structureClusterList(){
        return [];
    }
    /**
     * 
     * @param {Room} spawnRoom 
     */
    init(spawnRoom){
        this.map = new StructureMap({x:0,y:0}, []);

        let midPos = spawnRoom.getPositionAt(24,24);
        let controllerPos = spawnRoom.controller.pos;

        this.planStructure_spiral(spawnRoom.getPositionAt(Math.floor((midPos.x*2+controllerPos.x)/3),Math.floor((midPos.y*2+controllerPos.y)/3)));
    }

    serialize(){
        return {
            super: super.serialize(),
            map: this.map,
            structurePos: this.structurePos,
        }
    }

    deserialize(obj){
        super.deserialize(obj.super);
        try{
            this.map = new StructureMap();
            this.map.set(obj.map);
        }catch(e){
            errLog(e);
            this.map = new StructureMap({x:0,y:0}, []);
        }
        this.structurePos = obj.structurePos;
        return this;
    }

    /**
     * 
     * @abstract
     * @param {String} roomName 
     */
    colonize(roomName){}

    /**
     * 
     * @param {ResourceConstant} resourceType 
     * @param {Number} amount 
     */
    withdrawResource(resourceType = RESOURCE_ENERGY, amount = undefined){}

    /**
     * 
     * @abstract
     * @param {ResourceConstant} resourceType 
     */
    getFreeResource(resourceType = RESOURCE_ENERGY){}
    getPlanQueue_spiral(center = this.spawnRoom.getPositionAt(24,24)){
        let queue = [];
        let terrain = this.spawnRoom.getTerrain();

        const tryPush = (x,y)=>{
            try{
                let pos = new RoomPosition(center.x+x,center.y+y, this.spawnRoomName);
                if(terrain.get(pos.x, pos.y) !== TERRAIN_MASK_WALL){
                    queue.push(pos);
                }
            }catch{}
        }

        for(let r=1; r<roomSize; r++){
            let y=-r;

            for(let x=-r; x<=r; x++){
                tryPush(x,y);
            }
            for(; y<r; y++){
                tryPush(r,y);
                tryPush(-r,y);
            }
            for(let x=-r; x<=r; x++){
                tryPush(x,y);
            }
        }
        return queue;
    }
    /**
     * 
     * @param {RoomPosition|undefined} center 
     */
    planStructure_spiral(center){
        let queue;
        if(!center){
            queue = this.getPlanQueue_spiral(this.spawnRoom.controller.pos);
        }else{
            queue = this.getPlanQueue_spiral(center);
        }

        let i=0;
        for(let pos of queue){
            if(i>=this.structureClusterList.length){
                break;
            }
            let target = this.structureClusterList[i];
            if(!target.canFit(this.spawnRoom, pos)){
                continue;
            }
            this.map.plan(target, pos);
            this.structurePos[i] = pos;
            i++;
        }
        if(i<this.structureClusterList.length){
            console.log("can't fit all clusters");
        }
    }

    isStructureConstant(structureType){
        const typeList = [
            STRUCTURE_EXTENSION,// = "extension";
            STRUCTURE_RAMPART,// = "rampart";
            STRUCTURE_ROAD,// = "road";
            STRUCTURE_SPAWN,// = "spawn";
            STRUCTURE_LINK,// = "link";
            STRUCTURE_WALL,// = "constructedWall";
            STRUCTURE_KEEPER_LAIR,// = "keeperLair";
            STRUCTURE_CONTROLLER,// = "controller";
            STRUCTURE_STORAGE,// = "storage";
            STRUCTURE_TOWER,// = "tower";
            STRUCTURE_OBSERVER,// = "observer";
            STRUCTURE_POWER_BANK,// = "powerBank";
            STRUCTURE_POWER_SPAWN,// = "powerSpawn";
            STRUCTURE_EXTRACTOR,// = "extractor";
            STRUCTURE_LAB,// = "lab";
            STRUCTURE_TERMINAL,// = "terminal";
            STRUCTURE_CONTAINER,// = "container";
            STRUCTURE_NUKER,// = "nuker";
            STRUCTURE_FACTORY,// = "factory";
            STRUCTURE_INVADER_CORE,// = "invaderCore";
            STRUCTURE_PORTAL,// = "portal";
        ];
        return typeList.some((type)=>type === structureType);
    }

    run(){
        if(!this.map && this.spawnRoom.memory && this.spawnRoom.memory.enablePlanning){
            this.init(this.spawnRoom);
        }

        if(this.spawnRoom.controller.level>=2 && Game.time%200===190){
            if(!this.map.isCreated(this.spawnRoom)){
                this.map.build(this.spawnRoom);
            }
        }
        for(let name in Game.flags){
            let flag = Game.flags[name];
            if(!flag){
                continue;
            }

            let pos = flag.pos;
            let [cmd,cmdType,...args] = flag.name.split(' ');
            let plan;
            try{
                if(cmdType === "template"){
                    let templateName = args[0];
                    plan = structureClusterTemplates[templateName];
                    if(!(plan instanceof StructureCluster)){
                        throw new Error("invalid structure template");
                    }
                }else if(cmdType === "structure"){
                    let structureType = args[0];
                    if(!this.isStructureConstant(structureType)){
                        throw new Error("invalid structure constant");
                    }
                    plan = new StructurePlan(structureType);
                }else{
                    continue;
                    // throw new Error("invalid command type.\nUsage: [plan|remove] [template|structure]");
                }

                switch(cmd){
                    case "plan":
                        if(!plan.canFit(this.spawnRoom, pos)){
                            throw new Error("can't fit");
                        }
                    case "claim":
                        this.map.plan(plan, pos);
                        if(!plan.isCreated(this.spawnRoom, pos)){
                            plan.build(this.spawnRoom, pos);
                        }
                        break;
                    case "unclaim":
                    case "remove":
                        let list = this.map.lookAt(pos.x, pos.y);
                        for(let i=0; i<list.length; i++){
                            let build = list[i];
                            if(!build){
                                continue;
                            }
                            if(plan.isEqual(build) || cmd === "unclaim"){
                                list.splice(i, 1);
                                list = null;
                            }
                        }
                        if(list !== null){
                            errLog(new Error("no matched plan"));
                        }
                        break;
                    default:
                        continue;
                        // throw new Error("invalid command.\nUsage: [plan|remove] [template|structure]");

                }

                flag.remove();
            }catch(e){
                errLog(e);
            }
        }
    }
}